package com.qingsong.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.*;
import cn.hutool.crypto.SecureUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryChain;
import com.mybatisflex.core.query.QueryMethods;
import com.mybatisflex.core.relation.RelationManager;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.qingsong.component.MyRedis;
import com.qingsong.constant.ML;
import com.qingsong.dto.*;
import com.qingsong.entity.*;
import com.qingsong.excel.UserExcelDTO;
import com.qingsong.exception.ServiceException;
import com.qingsong.feign.CourseFeign;
import com.qingsong.mapper.UserMapper;
import com.qingsong.mapper.UserRoleMapper;
import com.qingsong.service.UserService;
import com.qingsong.util.JwtUtil;
import com.qingsong.util.MinioUtil;
import com.qingsong.util.MyUtil;
import com.qingsong.util.UserUtil;
import com.qingsong.vo.LoginVO;
import com.qingsong.vo.UserSimpleListVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.mybatisflex.core.query.QueryMethods.*;
import static com.qingsong.entity.table.MenuTableDef.MENU;
import static com.qingsong.entity.table.RoleMenuTableDef.ROLE_MENU;
import static com.qingsong.entity.table.RoleTableDef.ROLE;
import static com.qingsong.entity.table.UserRoleTableDef.USER_ROLE;
import static com.qingsong.entity.table.UserTableDef.USER;

/**
 * 用户表 服务层实现。
 *
 * @author qingsong
 * @since 2024-12-22
 */
@Service
@CacheConfig(cacheNames = "user")
public class UserServiceImpl extends ServiceImpl<UserMapper, User>  implements UserService{

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private MyRedis redis;

    @Autowired
    private CourseFeign courseFeign;

    @CacheEvict(allEntries = true)
    @Override
    public boolean insert(UserInsertDTO dto) {
        // 校验身份证号
        String idcard = dto.getIdcard();
        if (!IdcardUtil.isValidCard(idcard)) {
            throw new RuntimeException("身份证号格式有误");
        }
        // 账号查重
        if (QueryChain.of(mapper)
                .where(USER.USERNAME.eq(dto.getUsername()))
                .exists()) {
            throw new RuntimeException("账号已存在");
        }
        // 身份证号查重
        if (QueryChain.of(mapper)
                .where(USER.IDCARD.eq(dto.getIdcard()))
                .exists()) {
            throw new RuntimeException("身份证号已存在");
        }
        // 电话号码查重
        if (QueryChain.of(mapper)
                .where(USER.PHONE.eq(dto.getPhone()))
                .exists()) {
            throw new RuntimeException("手机号码已存在");
        }
        // 电子邮箱查重
        if (QueryChain.of(mapper)
                .where(USER.EMAIL.eq(dto.getEmail()))
                .exists()) {
            throw new RuntimeException("电子邮箱已存在");
        }
        // 组装 entity 实体类
        User user = BeanUtil.copyProperties(dto, User.class);
        user.setNickname(RandomUtil.randomString(10));
        user.setGender(UserUtil.defaultGender(idcard));
        user.setAge(UserUtil.defaultAge(idcard));
        user.setZodiac(UserUtil.defaultZodiac(idcard));
        user.setAvatar(UserUtil.defaultAvatar(idcard));
        user.setProvince(UserUtil.defaultProvince(idcard));
        user.setPassword(SecureUtil.md5(user.getPassword()));
        user.setCreated(LocalDateTime.now());
        user.setUpdated(LocalDateTime.now());
        return mapper.insert(user) > 0;
    }

    @Cacheable(key = "#p0", condition = "#p0 != null", unless = "#result == null")
    @Override
    public User select(Long id) {
        return UserUtil.desensitization(QueryChain.of(mapper)
                .where(USER.ID.eq(id))
                .withRelations()
                .one());
    }

    @Override
    @Cacheable(key = "#root.methodName", unless = "#result == null")
    //当不涉及联表时, 可以这么写 simpleList
    public List<UserSimpleListVO> simpleList() {
        return QueryChain.of(mapper)
                .withRelations()
                .listAs(UserSimpleListVO.class);
    }

    @Cacheable(key = "#root.methodName + ':' + #p0.toString()", unless = "#result == null")
    @Override
    public Page<User> page(UserPageDTO dto) {
        QueryChain<User> queryChain = QueryChain.of(mapper);
        // username条件
        String username = dto.getUsername();
        if (ObjectUtil.isNotNull(username)) {
            queryChain.where(USER.USERNAME.like(username));
        }
        // nickname条件
        String nickname = dto.getNickname();
        if (ObjectUtil.isNotNull(nickname)) {
            queryChain.where(USER.NICKNAME.like(nickname));
        }
        // phone条件
        String phone = dto.getPhone();
        if (ObjectUtil.isNotNull(phone)) {
            queryChain.where(USER.PHONE.like(phone));
        }
        // 分页查询
        Page<User> result = queryChain.withRelations()
                .page(new Page<>(dto.getPageNum(), dto.getPageSize()));
        // 脱敏
        result.setRecords(UserUtil.desensitization(result.getRecords()));
        return result;
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean update(UserUpdateDTO dto) {

        // 电子邮箱查重
        if (StrUtil.isNotEmpty(dto.getEmail()) && QueryChain.of(mapper)
                .where(USER.EMAIL.eq(dto.getEmail()))
                .and(USER.ID.ne(dto.getId()))
                .exists()) {
            throw new ServiceException("电子邮箱已存在");
        }

        // 组装 entity 实体类
        User user = BeanUtil.copyProperties(dto, User.class);
        user.setUpdated(LocalDateTime.now());

        return UpdateChain.of(user)
                .where(USER.ID.eq(user.getId()))
                .update();
    }

    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(allEntries = true)
    @Override
    public boolean delete(Long id) {
        // 删除中间表记录
        UpdateChain.of(userRoleMapper)
                .where(USER_ROLE.FK_USER_ID.eq(id))
                .remove();
        // 删除用户
        return mapper.deleteById(id) > 0;
    }

    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(allEntries = true)
    @Override
    public boolean deleteBatch(List<Long> ids) {
        // 删除中间表记录
        UpdateChain.of(userRoleMapper)
                .where(USER_ROLE.FK_USER_ID.in(ids))
                .remove();

        // 删除用户的评论记录
        courseFeign.deleteCommentByUserIds(ids);

        // 删除用户的举报记录
        courseFeign.deleteReportByUserIds(ids);

        // 删除用户
        return mapper.deleteBatchByIds(ids) == ids.size();
    }

    @Cacheable(key = "#root.methodName + ':' + #p0", condition = "#p0 != null", unless = "#result == null")
    @Override
    public List<Long> listRoleIds(Long id) {
        return QueryChain.of(UserRole.class)
                .select(USER_ROLE.FK_ROLE_ID)
                .where(USER_ROLE.FK_USER_ID.eq(id))
                .objListAs(Long.class);
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateRoles(UserUpdateDTO dto) {
        Long userId = dto.getId();
        List<Long> roleIds = dto.getRoleIds();
        // 在中间表中删除该用户的全部角色记录
        UpdateChain.of(userRoleMapper)
                .where(USER_ROLE.FK_USER_ID.eq(userId))
                .remove();
        // 新角色列表为空，直接返回即可
        if (CollUtil.isEmpty(roleIds)) {
            return true;
        }
        // 在中间表中批量添加该用户的新角色记录
        List<UserRole> userRoles = new ArrayList<>();
        for (Long roleId : roleIds) {
            UserRole userRole = new UserRole();
            userRole.setFkUserId(userId);
            userRole.setFkRoleId(roleId);
            userRole.setCreated(LocalDateTime.now());
            userRole.setUpdated(LocalDateTime.now());
            userRoles.add(userRole);
        }
        return userRoleMapper.insertBatch(userRoles) == roleIds.size();
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean resetPassword(Long id) {
        return UpdateChain.of(mapper)
                .set(USER.PASSWORD, SecureUtil.md5(ML.User.DEFAULT_PASSWORD))
                .where(USER.ID.eq(id))
                .update();
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean updatePassword(UserUpdatePasswordDTO dto) {
        Long userId = dto.getId();
        // 判断用户是否存在以及原密码是否正确
        if (!QueryChain.of(mapper)
                .where(USER.ID.eq(userId))
                .and(USER.PASSWORD.eq(SecureUtil.md5(dto.getOldPassword())))
                .exists()) {
            throw new ServiceException("用户不存在或原密码有误");
        }
        // 更新用户库修改密码
        return UpdateChain.of(mapper)
                .set(USER.PASSWORD, SecureUtil.md5(dto.getNewPassword()))
                .where(USER.ID.eq(userId))
                .update();
    }

    @Cacheable(value = "statistics", condition = "#someCondition")
    @Override
    public Map<String, Object> statistics() {
        Map<String, Object> result = new HashMap<>();

        // 统计用户性别比例
        result.put("genderCount", QueryChain.of(mapper)
                .select(USER.GENDER.as("name"), QueryMethods.count().as("value"))
                .groupBy(USER.GENDER)
                .orderBy(USER.GENDER.asc())
                .listAs(Map.class));

        // 统计今日用户数
        double todayCount = QueryChain.of(mapper)
                .where(dateDiff(currentDate(), dateFormat(USER.CREATED, "%Y-%m-%d")).eq(0))
                .count();

        // 统计昨日用户数
        double yesterdayCount = QueryChain.of(mapper)
                .where(dateDiff(currentDate(), dateFormat(USER.CREATED, "%Y-%m-%d")).eq(1))
                .count();

        // 统计今年用户数
        double thisYearCount = QueryChain.of(mapper)
                .where(year(USER.CREATED).eq(year(currentDate())))
                .count();

        // 统计去年用户总数
        double lastYearCount = QueryChain.of(mapper)
                .where(year(USER.CREATED).subtract(year(currentDate())).eq(-1))
                .count();

        result.put("todayCount", todayCount);
        result.put("yesterdayCount", yesterdayCount);
        result.put("dayIncrease", MyUtil.increase(todayCount, yesterdayCount));
        result.put("thisYearCount", thisYearCount);
        result.put("lastYearCount", lastYearCount);
        result.put("yearIncrease", MyUtil.increase(thisYearCount, lastYearCount));
        return result;
    }

    @Override
    public List<UserExcelDTO> excel() {
        // 查询全部用户记录
        List<User> users = QueryChain.of(mapper).list();
        // List<User> -> List<UserExcelDTO>
        List<UserExcelDTO> result = new ArrayList<>();
        users.forEach(user -> {
            // 拷贝用户基础信息
            UserExcelDTO userExcelDTO = BeanUtil.copyProperties(user, UserExcelDTO.class);
            // 处理性别：0女1男2保密
            userExcelDTO.setGender(ML.User.genderFormat(user.getGender()));
            // 数据脱敏
            userExcelDTO.setRealname(DesensitizedUtil.chineseName(user.getRealname()));
            userExcelDTO.setIdcard(DesensitizedUtil.idCardNum(user.getIdcard(), 6, 3));
            userExcelDTO.setPhone(DesensitizedUtil.mobilePhone(user.getPhone()));
            result.add(userExcelDTO);
        });
        return result;
    }
    @Override
    public String getVcode(String phone) {
        // 根据手机号码查询一名用户
        User user = QueryChain.of(mapper).where(USER.PHONE.eq(phone)).one();
        // 检查手机号码是否存在
        if (!QueryChain.of(mapper)
                .where(USER.PHONE.eq(phone))
                .exists()) {
            throw new ServiceException("手机号码不存在");
        }
        // 将短信验证码存入redis中，有效期5分钟
        String key = ML.Redis.VCODE_PREFIX + phone;
        String val = RandomUtil.randomNumbers(6);
        redis.setEx(key, val, 5, TimeUnit.MINUTES);
        // todo: 向指定手机号码发送验证码
        // 将短信验证码返回给客户端
        return val;
    }

    @Override
    public LoginVO loginByAccount(LoginByAccountDTO dto) {
        // 按账号密码查询用户记录
        User user = QueryChain.of(mapper)
                .where(USER.USERNAME.eq(dto.getUsername()))
                .and(USER.PASSWORD.eq(SecureUtil.md5(dto.getPassword())))
                .one();
        // 检查记录是否存在
        if (ObjectUtil.isNull(user)) {
            throw new ServiceException("账号密码错误");
        }
        return buildLoginVO(user);
    }

    @Override
    public LoginVO loginByPhone(LoginByPhoneDTO dto) {
        // 校验验证码是否有效
        String key = ML.Redis.VCODE_PREFIX + dto.getPhone();
        String vcodeFromRedis = redis.get(key);
        if (ObjectUtil.isNull(vcodeFromRedis) || !vcodeFromRedis.equals(dto.getVcode())) {
            throw new ServiceException("验证码无效");
        }

        // 根据手机号码查询用户记录
        User user = QueryChain.of(mapper)
                .where(USER.PHONE.eq(dto.getPhone()))
                .one();

        if (ObjectUtil.isNull(user)) {
            throw new ServiceException("手机号码不存在");
        }

        return buildLoginVO(user);
    }

    /*
    //自己写的buildLoginVO, 用于账号密码登录
    private LoginVO buildLoginVO(User user) {
        // 查询用户角色ID列表
        List<Long> roleIds = QueryChain.of(UserRole.class)
                .select(USER_ROLE.FK_ROLE_ID)
                .where(USER_ROLE.FK_USER_ID.eq(user.getId()))
                .objListAs(Long.class);

        // 查询角色标题列表
        List<String> roleTitles = QueryChain.of(Role.class)
                .select(ROLE.TITLE)
                .where(ROLE.ID.in(roleIds))
                .objListAs(String.class);

        // 查询用户菜单ID列表
        List<Long> menuIds = QueryChain.of(RoleMenu.class)
                .select(ROLE_MENU.FK_MENU_ID)
                .where(ROLE_MENU.FK_ROLE_ID.in(roleIds))
                .objListAs(Long.class);

       // 查询菜单详情列表
        List<Menu> menus = QueryChain.of(Menu.class)
                .where(MENU.ID.in(menuIds))
                .list();

        // 查询菜单详情列表，包括子菜单
        List<Menu> menus = fetchMenusWithSubMenus(menuIds);

        //组装VO对象
        LoginVO loginVO = new LoginVO();
        loginVO.setUser(user);
        loginVO.setRoleTitles(roleTitles);
        loginVO.setMenus(menus);
        loginVO.setToken(JwtUtil.build(user.getId(),user.getUsername(),user.getAvatar()));
        return loginVO;
    }

*/

    /**
     * 组装LoginVO
     * @param user 用户实体
     * @return LoginVO：包含用户信息，对应该用户的Token令牌，角色标题列表以及菜单列表
     */
    private LoginVO buildLoginVO(User user) {
        // 查询用户角色ID列表
        // select fk_role_id from user_role where fk_user_id = ?
        List<Long> roleIds = QueryChain.of(UserRole.class)
                .select(USER_ROLE.FK_ROLE_ID)
                .where(USER_ROLE.FK_USER_ID.eq(user.getId()))
                .objListAs(Long.class);

        // 查询角色标题列表
        // select title from role where id in (1, 2, 3, 4, 5)
        List<String> roleTitles = null;
        if (roleIds != null && !roleIds.isEmpty()) {
            roleTitles = QueryChain.of(Role.class)
                    .select(ROLE.TITLE)
                    .where(ROLE.ID.in(roleIds))
                    .objListAs(String.class);
        }
        // 查询用户菜单ID列表
        // select fk_menu_id from role_menu where fk_role_id in (1, 2, 3, 4, 5)
        List<Long> menuIds = null;
        if (roleIds != null && !roleIds.isEmpty()) {
            menuIds = QueryChain.of(RoleMenu.class)
                    .select(ROLE_MENU.FK_MENU_ID)
                    .where(ROLE_MENU.FK_ROLE_ID.in(roleIds))
                    .objListAs(Long.class);
        }

        // 查询用户菜单列表，只查询父菜单，级联子菜单
        // select * from menu where id in (1, 2, 3, 4, 5) and pid = 0 order by idx asc, id desc
        RelationManager.addIgnoreRelations("parentMenu");
        List<Menu> menus = QueryChain.of(Menu.class)
                .where(MENU.ID.in(menuIds))
                .and(MENU.PID.eq(ML.Menu.ROOT_ID))
                .orderBy(MENU.IDX.asc(), MENU.ID.desc())
                .withRelations()
                .list();

        // 组装VO
        LoginVO result = new LoginVO();
        result.setRoleTitles(roleTitles);
        result.setMenus(menus);
        result.setUser(UserUtil.desensitization(user));
        result.setToken(JwtUtil.build(user.getId(), user.getNickname(), user.getAvatar()));
        return result;
    }

    //把子菜单也查出来
    private List<Menu> fetchMenusWithSubMenus(List<Long> menuIds) {
        List<Menu> allMenus = new ArrayList<>();
        for (Long menuId : menuIds) {
            Menu menu = QueryChain.of(Menu.class)
                    .where(MENU.ID.eq(menuId))
                    .one();
            if (menu != null) {
                // 递归获取子菜单
                List<Long> subMenuIds = QueryChain.of(Menu.class)
                        .select(MENU.ID)
                        .where(MENU.PID.eq(menu.getId()))
                        .objListAs(Long.class);
                menu.setSubMenus(fetchMenusWithSubMenus(subMenuIds));
                allMenus.add(menu);
            }
        }
        return allMenus;
    }

    @Override
    @CacheEvict(allEntries = true)
    public String uploadAvatar(MultipartFile avatarFile, Long id) {
        // 按主键查询用户记录
        User user = mapper.selectOneById(id);
        if (ObjectUtil.isNull(user)) {
            throw new ServiceException("用户不存在");
        }
        // 备份旧头像
        String oldAvatar = user.getAvatar();
        // 重新生成文件名
        String filename = MinioUtil.randomFilename(avatarFile);
        // 根据用户主键修改用户头像
        user.setAvatar(filename);
        if (mapper.update(user) <= 0) {
            throw new ServiceException("头像更新失败：DB操作异常");
        }
        try {
            // 从MinIO中删除旧头像：默认头像不删除
            if (!ML.User.DEFAULT_AVATARS.contains(oldAvatar)) {
                MinioUtil.delete(oldAvatar, ML.MinIO.AVATAR_DIR, ML.MinIO.BUCKET_NAME);
            }
            // 向MinIO中上传新头像
            MinioUtil.upload(avatarFile, filename, ML.MinIO.AVATAR_DIR, ML.MinIO.BUCKET_NAME);
        } catch (Exception e) {
            throw new ServiceException("头像更新失败：MinIO操作异常");
        }
        // 返回文件名
        return filename;
    }
}
